/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.spring.scope;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

/**
 * a {@link Scope} that create an {@link Object} by thread.
 */
public class ThreadScope
    implements Scope
{

  /**
   * the {@link ThreadLocal} containing the datas for each thread.
   */
  private transient final ThreadLocal<ThreadScopeData> threadScopeDatas = new ThreadLocal<ThreadScopeData>();

  /**
   * the actual scope that keep the cached bean. It will be used through a
   * {@link ThreadLocal} to be different for each thread.
   */
  private class ThreadScopeData
      implements Scope
  {
    /**
     * /** Called by the garbage collector on an object when garbage collection
     * determines that there are no more references to the object. A subclass
     * overrides the <code>finalize</code> method to dispose of system
     * resources or to perform other cleanup.
     * <p>
     * The general contract of <tt>finalize</tt> is that it is invoked if and
     * when the Java<font size="-2"><sup>TM</sup></font> virtual machine has
     * determined that there is no longer any means by which this object can be
     * accessed by any thread that has not yet died, except as a result of an
     * action taken by the finalization of some other object or class which is
     * ready to be finalized. The <tt>finalize</tt> method may take any
     * action, including making this object available again to other threads;
     * the usual purpose of <tt>finalize</tt>, however, is to perform cleanup
     * actions before the object is irrevocably discarded. For example, the
     * finalize method for an object that represents an input/output connection
     * might perform explicit I/O transactions to break the connection before
     * the object is permanently discarded.
     * <p>
     * The <tt>finalize</tt> method of class <tt>Object</tt> performs no
     * special action; it simply returns normally. Subclasses of <tt>Object</tt>
     * may override this definition.
     * <p>
     * The Java programming language does not guarantee which thread will invoke
     * the <tt>finalize</tt> method for any given object. It is guaranteed,
     * however, that the thread that invokes finalize will not be holding any
     * user-visible synchronization locks when finalize is invoked. If an
     * uncaught exception is thrown by the finalize method, the exception is
     * ignored and finalization of that object terminates.
     * <p>
     * After the <tt>finalize</tt> method has been invoked for an object, no
     * further action is taken until the Java virtual machine has again
     * determined that there is no longer any means by which this object can be
     * accessed by any thread that has not yet died, including possible actions
     * by other objects or classes which are ready to be finalized, at which
     * point the object may be discarded.
     * <p>
     * The <tt>finalize</tt> method is never invoked more than once by a Java
     * virtual machine for any given object.
     * <p>
     * Any exception thrown by the <code>finalize</code> method causes the
     * finalization of this object to be halted, but is otherwise ignored.
     * 
     * @throws Throwable
     *           the <code>Exception</code> raised by this method
     * @see java.lang.Object#finalize()
     */
    @Override
    protected void finalize()
        throws Throwable
    {
      try {
        for (Runnable destructionCallback : destructionCallbacks.values()) {
          destructionCallback.run();
        }
      }
      finally {
        super.finalize();
      }
    }

    /**
     * the registered descruction callbacks.
     */
    private transient final Map<String, Runnable> destructionCallbacks = new HashMap<String, Runnable>();

    /**
     * the cached bean in this scope.
     */
    private transient final Map<String, Object> datas = new HashMap<String, Object>();



    /**
     * Return the object with the given name from the underlying scope,
     * {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
     * if not found in the underlying storage mechanism.
     * <p>
     * This is the central operation of a Scope, and the only operation that is
     * absolutely required.
     * 
     * @param name
     *          the name of the object to retrieve
     * @param objectFactory
     *          the {@link ObjectFactory} to use to create the scoped object if
     *          it is not present in the underlying storage mechanism
     * @return the desired object (never <code>null</code>)
     * @see org.springframework.beans.factory.config.Scope#get(java.lang.String,
     *      org.springframework.beans.factory.ObjectFactory)
     */
    public Object get(String name, ObjectFactory objectFactory)
    {
      Object res = datas.get(name);
      if (res == null) {
        res = objectFactory.getObject();
        datas.put(name, res);
      }
      return res;
    }



    /**
     * Return the <em>conversation ID</em> for the current underlying scope,
     * if any.
     * <p>
     * The exact meaning of the conversation ID depends on the underlying
     * storage mechanism. In the case of session-scoped objects, the
     * conversation ID would typically be equal to (or derived from) the
     * {@link javax.servlet.http.HttpSession#getId() session ID}; in the case
     * of a custom conversation that sits within the overall session, the
     * specific ID for the current conversation would be appropriate.
     * <p>
     * <b>Note: This is an optional operation.</b> It is perfectly valid to
     * return <code>null</code> in an implementation of this method if the
     * underlying storage mechanism has no obvious candidate for such an ID.
     * 
     * @return the conversation ID, or <code>null</code> if there is no
     *         conversation ID for the current scope
     * @see org.springframework.beans.factory.config.Scope#getConversationId()
     */
    public String getConversationId()
    {
      return null;
    }



    /**
     * Register a callback to be executed on destruction of the specified object
     * in the scope (or at destruction of the entire scope, if the scope does
     * not destroy individual objects but rather only terminates in its
     * entirety).
     * <p>
     * <b>Note: This is an optional operation.</b> This method will only be
     * called for scoped beans with actual destruction configuration
     * (DisposableBean, destroy-method, DestructionAwareBeanPostProcessor).
     * Implementations should do their best to execute a given callback at the
     * appropriate time. If such a callback is not supported by the underlying
     * runtime environment at all, the callback <i>must be ignored and a
     * corresponding warning should be logged</i>.
     * <p>
     * Note that 'destruction' refers to to automatic destruction of the object
     * as part of the scope's own lifecycle, not to the individual scoped object
     * having been explicitly removed by the application. If a scoped object
     * gets removed via this facade's {@link #remove(String)} method, any
     * registered destruction callback should be removed as well, assuming that
     * the removed object will be reused or manually destroyed.
     * 
     * @param name
     *          the name of the object to execute the destruction callback for
     * @param callback
     *          the destruction callback to be executed. Note that the passed-in
     *          Runnable will never throw an exception, so it can safely be
     *          executed without an enclosing try-catch block. Furthermore, the
     *          Runnable will usually be serializable, provided that its target
     *          object is serializable as well.
     * @see org.springframework.beans.factory.DisposableBean
     * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName()
     * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
     * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String,
     *      java.lang.Runnable)
     */
    public void registerDestructionCallback(String name, Runnable callback)
    {
      destructionCallbacks.put(name, callback);
    }



    /**
     * Remove the object with the given <code>name</code> from the underlying
     * scope.
     * <p>
     * Returns <code>null</code> if no object was found; otherwise returns the
     * removed <code>Object</code>.
     * <p>
     * Note that an implementation should also remove a registered destruction
     * callback for the specified object, if any. It does, however, <i>not</i>
     * need to <i>execute</i> a registered destruction callback in this case,
     * since the object will be destroyed by the caller (if appropriate).
     * <p>
     * <b>Note: This is an optional operation.</b> Implementations may throw
     * {@link UnsupportedOperationException} if they do not support explicitly
     * removing an object.
     * 
     * @param name
     *          the name of the object to remove
     * @return the removed object, or <code>null</code> if no object was
     *         present
     * @see #registerDestructionCallback
     * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
     */
    public Object remove(String name)
    {
      destructionCallbacks.remove(name);
      return datas.remove(name);
    }

  }



  /**
   * returns the {@link ThreadScopeData} associated to the current thread.
   * 
   * @return the {@link ThreadScopeData} associated to the current thread
   */
  private ThreadScopeData getThreadScopeData()
  {
    ThreadScopeData threadScopeData = threadScopeDatas.get();
    if (threadScopeData == null) {
      threadScopeData = new ThreadScopeData();
      threadScopeDatas.set(threadScopeData);
    }
    return threadScopeData;
  }



  /**
   * Return the object with the given name from the underlying scope,
   * {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
   * if not found in the underlying storage mechanism.
   * <p>
   * This is the central operation of a Scope, and the only operation that is
   * absolutely required.
   * 
   * @param name
   *          the name of the object to retrieve
   * @param objectFactory
   *          the {@link ObjectFactory} to use to create the scoped object if it
   *          is not present in the underlying storage mechanism
   * @return the desired object (never <code>null</code>)
   * @see org.springframework.beans.factory.config.Scope#get(java.lang.String,
   *      org.springframework.beans.factory.ObjectFactory)
   */
  public Object get(String name, ObjectFactory objectFactory)
  {
    return getThreadScopeData().get(name, objectFactory);
  }



  /**
   * Return the <em>conversation ID</em> for the current underlying scope, if
   * any.
   * <p>
   * The exact meaning of the conversation ID depends on the underlying storage
   * mechanism. In the case of session-scoped objects, the conversation ID would
   * typically be equal to (or derived from) the
   * {@link javax.servlet.http.HttpSession#getId() session ID}; in the case of
   * a custom conversation that sits within the overall session, the specific ID
   * for the current conversation would be appropriate.
   * <p>
   * <b>Note: This is an optional operation.</b> It is perfectly valid to
   * return <code>null</code> in an implementation of this method if the
   * underlying storage mechanism has no obvious candidate for such an ID.
   * 
   * @return the conversation ID, or <code>null</code> if there is no
   *         conversation ID for the current scope
   * @see org.springframework.beans.factory.config.Scope#getConversationId()
   */
  public String getConversationId()
  {
    return null;
  }



  /**
   * Register a callback to be executed on destruction of the specified object
   * in the scope (or at destruction of the entire scope, if the scope does not
   * destroy individual objects but rather only terminates in its entirety).
   * <p>
   * <b>Note: This is an optional operation.</b> This method will only be
   * called for scoped beans with actual destruction configuration
   * (DisposableBean, destroy-method, DestructionAwareBeanPostProcessor).
   * Implementations should do their best to execute a given callback at the
   * appropriate time. If such a callback is not supported by the underlying
   * runtime environment at all, the callback <i>must be ignored and a
   * corresponding warning should be logged</i>.
   * <p>
   * Note that 'destruction' refers to to automatic destruction of the object as
   * part of the scope's own lifecycle, not to the individual scoped object
   * having been explicitly removed by the application. If a scoped object gets
   * removed via this facade's {@link #remove(String)} method, any registered
   * destruction callback should be removed as well, assuming that the removed
   * object will be reused or manually destroyed.
   * 
   * @param name
   *          the name of the object to execute the destruction callback for
   * @param callback
   *          the destruction callback to be executed. Note that the passed-in
   *          Runnable will never throw an exception, so it can safely be
   *          executed without an enclosing try-catch block. Furthermore, the
   *          Runnable will usually be serializable, provided that its target
   *          object is serializable as well.
   * @see org.springframework.beans.factory.DisposableBean
   * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName()
   * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
   * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String,
   *      java.lang.Runnable)
   */
  public void registerDestructionCallback(String name, Runnable callback)
  {
    getThreadScopeData().registerDestructionCallback(name, callback);
  }



  /**
   * Remove the object with the given <code>name</code> from the underlying
   * scope.
   * <p>
   * Returns <code>null</code> if no object was found; otherwise returns the
   * removed <code>Object</code>.
   * <p>
   * Note that an implementation should also remove a registered destruction
   * callback for the specified object, if any. It does, however, <i>not</i>
   * need to <i>execute</i> a registered destruction callback in this case,
   * since the object will be destroyed by the caller (if appropriate).
   * <p>
   * <b>Note: This is an optional operation.</b> Implementations may throw
   * {@link UnsupportedOperationException} if they do not support explicitly
   * removing an object.
   * 
   * @param name
   *          the name of the object to remove
   * @return the removed object, or <code>null</code> if no object was present
   * @see #registerDestructionCallback
   * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
   */
  public Object remove(String name)
  {
    return getThreadScopeData().remove(name);
  }

}
